﻿using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.Reflection;
using System.IO;
using Microsoft.CSharp;

namespace Swd05.SH.Bll
{
	public class ScriptEngine : Interfaces.IScriptEngine
	{
		private List<string>		imports		= new List<string>();
		private object context					= null;
		private CSharpCodeProvider provider	= new CSharpCodeProvider();
		private CompilerParameters cp			= new CompilerParameters();
		private CompilerResults    cr			= null;
		private string					text		= string.Empty;
		private object					instance	= null;
		
		#region Methods

		private ScriptEngine()
		{
		}
		public ScriptEngine( object context, string script )
		{
			if( context == null )
			{
				throw new ArgumentException( "ScriptEngine.Context cannot be <null>!" );
			}
			this.context = context;
			this.Text = script;
			
			// Compiler aus Codeprovider erzeugen;
			this.CompilerParameters.GenerateExecutable = false;
			this.CompilerParameters.GenerateInMemory = true;
			this.CompilerParameters.IncludeDebugInformation = false;
			this.CompilerParameters.CompilerOptions = this.CompilerOptions;

			this.AddReferencedAssembly("System.dll");
			this.AddReferencedAssembly("System.Xml.dll");
			this.AddReferencedAssembly("System.Data.dll");
			
		}
		public object Eval(string method)
		{
			object result = null;
			if (this.Text.Length > 0)
			{
				if (this.Instance == null)
				{
					this.Compile();
				}
				if (this.Instance != null)
				{
					if (this.Instance.GetType().GetMethod(method, System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod) != null)
					{
						result = this.Instance.GetType().InvokeMember(method, System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.InvokeMethod, null, this.Instance, null);
					}
				}
			}
			return result;
		}
		private void AddReferencedAssembly( string assemblyName )
		{
			if( ! this.CompilerParameters.ReferencedAssemblies.Contains( assemblyName ) )
			{
				this.CompilerParameters.ReferencedAssemblies.Add( assemblyName );
			}
		}
		private void AddNamespace( string usingNamespace )
		{
			this.ImportedNamespaces.Add( usingNamespace );
		}
		private string Compile( )
		{
			return this.Compile( this.Text );
		}
		private string Compile( string source)
		{
			StringBuilder errors = new StringBuilder();
			String text = string.Empty;
			if (this.CompilerResults == null || this.CompilerResults.CompiledAssembly == null)
			{
				text = this.Transform( source );
				this.CompilerResults = this.CodeProvider.CompileAssemblyFromSource(this.CompilerParameters, text );
				if (!this.CompilerResults.Errors.HasErrors)
				{
					this.Instance = this.CompilerResults.CompiledAssembly.CreateInstance("Swd05.SH.Bll.Script");
					this.Instance.GetType().InvokeMember("Engine", System.Reflection.BindingFlags.Default | System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.SetProperty, null, this.Instance, new object[] { this });

				}
				else
				{
					errors = new StringBuilder();
					foreach (CompilerError error in this.CompilerResults.Errors)
					{
						errors.AppendFormat("{0} at line {1} column {2}\r", error.ErrorText, error.Line, error.Column);
					}
					Console.WriteLine( text );
					Console.WriteLine(errors.ToString());
					
				}
			}
			return errors.ToString();
		}
		private string Transform( string text )
		{
			StringBuilder sb = new StringBuilder();
			StringBuilder sbScript = new StringBuilder();
			StringBuilder errors = new StringBuilder();
			string script = string.Empty;
			string template = string.Empty;
			string line = string.Empty;

			using (StringReader sr = new StringReader( text ) )
			{
				while (true)
				{
					line = sr.ReadLine();
					if (line == null)
					{
						break;
					}
					if (line.Length == 0)
					{
						continue;
					}
					if (line.StartsWith("r:"))
					{
						line = line.Replace("r:", "");
						this.AddReferencedAssembly( line );
						continue;
					}
					else if (line.StartsWith("u:"))
					{
						line = line.Replace("u:", "");
						this.AddNamespace( line );
						continue;
					}
					else
					{
						sbScript.Append(string.Format("{0}\r\n", line));
					}
				}
			}
			foreach (string s in this.ImportedNamespaces )
			{
				sb.Append(string.Format("using {0};\r\n", s));
			}
			sb.Append(this.Template);

			script = sb.ToString();
			script = script.Replace("[inject_code_here]", sbScript.ToString());


			return  script;
		}
		#endregion
		#region Properties
		private CompilerResults CompilerResults
		{
			get
			{
				return this.cr;
			}
			set
			{
				this.cr = value;
			}
		}
		private string CompilerOptions
		{
			get
			{
				StringBuilder sb = new StringBuilder();
				string dir = string.Empty;

				Assembly a = null;

				a = Assembly.GetExecutingAssembly();

				dir = Path.GetDirectoryName(a.Location);
				sb.Append("\"");
				sb.Append("/lib:");
				sb.Append(dir);
				sb.Append("\"");
				sb.Append(" /t:library");
				return sb.ToString();

			}
		}
		private CompilerParameters CompilerParameters
		{
			get
			{
				return this.cp;
			}
		}
		private CSharpCodeProvider CodeProvider
		{
			get
			{
				return this.provider;
			}
		}
		public object Context
		{
			get
			{
				return this.context;
			}
		}
		private List<String> ImportedNamespaces
		{
			get
			{
				return this.imports;
			}
		}
		private string Template
		{
			get
			{
				string template = string.Empty;
				using (StreamReader sr = new StreamReader(Assembly.GetExecutingAssembly().GetManifestResourceStream("Swd05.SH.Resourcen.Script.template"), System.Text.Encoding.Default))
				{
					template = sr.ReadToEnd();
				}
				return template;
			}
		}
		private string Text
		{
			get
			{
				return this.text;
			}
			set
			{
				this.text = value;
			}
		}
		/// <summary>
		/// Eine Instanz der Klasse Script, die aus dem Template
		/// erzeugt wird.
		/// </summary>
		private object Instance
		{
			get
			{
				return this.instance;
			}
			set 
			{
				this.instance = value;
			}
		}
		#endregion
		
	}
}
